package org.xteam.sled.semantic;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.xteam.parser.runtime.Span;
import org.xteam.sled.ast.EqOp;
import org.xteam.sled.model.BasePattern;
import org.xteam.sled.model.LabelledPattern;
import org.xteam.sled.model.LatentPattern;
import org.xteam.sled.semantic.exp.Exp;
import org.xteam.sled.semantic.exp.ExpForce;
import org.xteam.sled.semantic.exp.ExpInstanceInput;
import org.xteam.sled.semantic.exp.ExpInstanceTagged;
import org.xteam.sled.semantic.exp.ExpVar;
import org.xteam.sled.semantic.exp.VariableSubstitute;


public class Operand implements IDenotable {

	private String name;
	private Field field;
	private boolean relocatable;
	private OperandType type;

	public Operand(String name, OperandType type, Field field, boolean relocatable) {
		this.name = name;
		this.field = field;
		this.relocatable = relocatable;
		this.type = type;
	}

	public String name() {
		return name;
	}
	
	public OperandType getType() {
		return type;
	}
	
	public Field getField() {
		return field;
	}
	
	public boolean isRelocatable() {
		return relocatable;
	}
	
	@Override
	public Exp projectInt(IEvaluationContext context, Span span) {
		ExpVar parameter = new ExpVar(name);
		Exp base = relocatable ? new ExpForce(parameter) : parameter;
		return type.projectInt(context, span, base);
	}
	
	@Override
	public BasePattern projectPattern(IEvaluationContext context, Span span) {
		if (field != null) {
			return LabelledPattern.constraint(context, span, field, new EqOp(null), projectInt(context, span));
		}
		if (type instanceof InstanceOperand) {
			return constypePattern (new ExpVar(name), ((InstanceOperand)type).getConstructorType()).injectLatent();
		}
		context.reportError(span, "denotable " + name + " cannot be interpreted as a pattern");
		return null;
	}
	
	private LatentPattern constypePattern(ExpVar instance,
			ConstructorType constructorType) {
		//validateInstance(instance);
		LatentPattern p = null;
		for (InstructionConstructor cnst : constructorType.getMembers()) {
			LatentPattern res = consPat(instance, cnst);
			p = p == null ? res : p.or(res);
		}
		return LatentPattern.latentLabel(instance).seq(p);
	}

	private LatentPattern consPat(Exp instance, InstructionConstructor cons) {
		Map<String, Exp> sigma = new HashMap<String, Exp>();
		for (Operand op : cons.getOperands()) {
			Exp e = new ExpInstanceInput(instance, cons, op.name());
			if (op.isRelocatable())
				e = new ExpForce(e);
			sigma.put(op.name(), e);
		}
		LatentPattern q = (LatentPattern)consPattern(cons).subst(new VariableSubstitute(sigma));
		List<Exp> conds = new ArrayList<Exp>();
		conds.add(new ExpInstanceTagged(instance, cons, 0));
		q.addConditions(conds);
		return q;
	}
	
	private LatentPattern consPattern(InstructionConstructor cons) {
		if (cons.getBranches().isEmpty())
			throw new RuntimeException("constructor without branches");
		LatentPattern p = null;
		for (LatentPattern br : cons.getBranches()) {
			p = p == null ? br : p.or(br);
		}
		return (LatentPattern)p.subst(new VariableSubstitute(parameterSubstitution(cons)));
	}
	
	private Map<String, Exp> parameterSubstitution(InstructionConstructor cons) {
		Map<String, Exp> map = new HashMap<String, Exp>();
		for (Operand op : cons.getOperands()) {
			Exp e = new ExpVar(op.name());
			map.put(op.name(), op.getType().makeExpression(e, op.isRelocatable()));
		}
	    return map; // in  fn n => StringMap.find(map, n);
	}

	public String toString() {
		return "[" + name + "]";
	}

}
